2 * Copyright (c) 2017 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
26 #import "SFSignInAnalytics.h"
27 #import "SFSignInAnalytics+Internal.h"
29 #import <Analytics/SFAnalytics+Signin.h>
30 #import "Analytics/SFAnalyticsDefines.h"
31 #import "Analytics/SFAnalyticsSQLiteStore.h"
32 #import "Analytics/SFAnalytics.h"
34 #import <os/log_private.h>
35 #import <mach/mach_time.h>
36 #import <utilities/SecFileLocations.h>
37 #import "utilities/debugging.h"
38 #import <utilities/SecCFWrappers.h>
40 //metrics database location
41 NSString* signinMetricsDatabase = @"signin_metrics";
43 //defaults write results location
44 static NSString* const SFSignInAnalyticsDumpLoggedResultsToLocation = @"/tmp/signin_results.txt";
45 static NSString* const SFSignInAnalyticsPersistedEventList = @"/tmp/signin_eventlist";
48 static NSString* const SFSignInAnalyticsAttributeRecoverableError = @"recoverableError";
49 static NSString* const SFSignInAnalyticsAttributeErrorDomain = @"errorDomain";
50 static NSString* const SFSignInAnalyticsAttributeErrorCode = @"errorCode";
51 static NSString* const SFSignInAnalyticsAttributeErrorChain = @"errorChain";
52 static NSString* const SFSignInAnalyticsAttributeParentUUID = @"parentUUID";
53 static NSString* const SFSignInAnalyticsAttributeMyUUID = @"myUUID";
54 static NSString* const SFSignInAnalyticsAttributeSignInUUID = @"signinUUID";
55 static NSString* const SFSignInAnalyticsAttributeEventName = @"eventName";
56 static NSString* const SFSignInAnalyticsAttributeSubsystemName = @"subsystemName";
57 static NSString* const SFSignInAnalyticsAttributeBuiltDependencyChains = @"dependencyChains";
58 static NSString* const SFSignInAnalyticsAttributeTrackerTime = @"trackerTime";
60 @implementation SFSIALoggerObject
61 + (NSString*)databasePath {
62 return [SFSIALoggerObject defaultAnalyticsDatabasePath:signinMetricsDatabase];
65 + (instancetype)logger
67 return [super logger];
72 @interface SFSignInAnalytics ()
73 @property (nonatomic, copy) NSString *signin_uuid;
74 @property (nonatomic, copy) NSString *my_uuid;
75 @property (nonatomic, copy) NSString *parent_uuid;
76 @property (nonatomic, copy) NSString *category;
77 @property (nonatomic, copy) NSString *eventName;
78 @property (nonatomic, copy) NSString *persistencePath;
80 @property (nonatomic, strong) NSURL *persistedEventPlist;
81 @property (nonatomic, strong) NSMutableDictionary *eventDependencyList;
82 @property (nonatomic, strong) NSMutableArray *builtDependencyChains;
84 @property (nonatomic) BOOL canceled;
85 @property (nonatomic) BOOL stopped;
87 @property (nonatomic, strong) os_log_t logObject;
89 @property (nonatomic, strong) NSNumber *measurement;
91 @property (nonatomic, strong) dispatch_queue_t queue;
93 @property (nonatomic, strong) SFSignInAnalytics *root;
94 @property (nonatomic, strong) SFAnalyticsActivityTracker *tracker;
96 -(os_log_t) newLogForCategoryName:(NSString*) category;
97 -(os_log_t) logForCategoryName:(NSString*) category;
101 static NSMutableDictionary *logObjects;
102 static const NSString* signInLogSpace = @"com.apple.security.wiiss";
104 @implementation SFSignInAnalytics
106 + (BOOL)supportsSecureCoding {
110 -(os_log_t) logForCategoryName:(NSString*) category
112 return logObjects[category];
115 -(os_log_t) newLogForCategoryName:(NSString*) category
117 return os_log_create([signInLogSpace UTF8String], [category UTF8String]);
120 - (BOOL)writeDependencyList:(NSError**)error
122 NSError *localError = nil;
123 if (![NSPropertyListSerialization propertyList: self.root.eventDependencyList isValidForFormat: NSPropertyListXMLFormat_v1_0]){
124 os_log_error(self.logObject, "can't save PersistentState as XML");
128 NSData *data = [NSPropertyListSerialization dataWithPropertyList: self.root.eventDependencyList
129 format: NSPropertyListXMLFormat_v1_0 options: 0 error: &localError];
131 os_log_error(self.logObject, "error serializing PersistentState to xml: %@", localError);
135 BOOL writeStatus = [data writeToURL:self.root.persistedEventPlist options: NSDataWritingAtomic error: &localError];
137 os_log_error(self.logObject, "error writing PersistentState to file: %@", localError);
139 if(localError && error){
146 - (instancetype)initWithSignInUUID:(NSString *)uuid category:(NSString *)category eventName:(NSString*)eventName
154 _eventName = eventName;
155 _category = category;
159 _builtDependencyChains = [NSMutableArray array];
161 if ([self writeResultsToTmp]) {
162 //make plist file containing uuid parent/child
163 _persistencePath = [NSString stringWithFormat:@"%@-%@.plist", SFSignInAnalyticsPersistedEventList, eventName];
164 _persistedEventPlist = [NSURL fileURLWithPath:_persistencePath isDirectory:NO];
167 _eventDependencyList = [NSMutableDictionary dictionary];
168 [_eventDependencyList setObject:[NSMutableArray array] forKey:_signin_uuid];
170 _tracker = [[SFSIALoggerObject logger] logSystemMetricsForActivityNamed:eventName withAction:nil];
173 NSError* error = nil;
175 if(self.root.persistedEventPlist && ![self writeDependencyList:&error] ){
176 os_log(self.logObject, "attempting to write dependency list: %@", error);
179 _queue = dispatch_queue_create("com.apple.security.SignInAnalytics", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
181 static dispatch_once_t onceToken;
182 dispatch_once(&onceToken, ^{
183 logObjects = [NSMutableDictionary dictionary];
185 @synchronized(logObjects){
187 _logObject = [self logForCategoryName:category];
190 _logObject = [self newLogForCategoryName:category];
191 [logObjects setObject:_logObject forKey:category];
199 -(instancetype) initChildWithSignInUUID:(NSString*)uuid andCategory:(NSString*)category andEventName:(NSString*)eventName
207 _eventName = eventName;
208 _category = category;
214 - (void)encodeWithCoder:(NSCoder *)coder {
215 [coder encodeObject:_signin_uuid forKey:@"UUID"];
216 [coder encodeObject:_category forKey:@"category"];
217 [coder encodeObject:_parent_uuid forKey:@"parentUUID"];
218 [coder encodeObject:_my_uuid forKey:@"myUUID"];
219 [coder encodeObject:_measurement forKey:@"measurement"];
220 [coder encodeObject:_eventName forKey:@"eventName"];
223 - (nullable instancetype)initWithCoder:(NSCoder *)decoder
227 _signin_uuid = [decoder decodeObjectOfClass:[NSString class] forKey:@"UUID"];
228 _category = [decoder decodeObjectOfClass:[NSString class] forKey:@"category"];
229 _parent_uuid = [decoder decodeObjectOfClass:[NSString class] forKey:@"parentUUID"];
230 _my_uuid = [decoder decodeObjectOfClass:[NSString class] forKey:@"myUUID"];
231 _measurement = [decoder decodeObjectOfClass:[NSString class] forKey:@"measurement"];
232 _eventName = [decoder decodeObjectOfClass:[NSString class] forKey:@"eventName"];
233 _queue = dispatch_queue_create("com.apple.security.SignInAnalytics", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
235 if(_signin_uuid == nil ||
237 _parent_uuid == nil){
238 [decoder failWithError:[NSError errorWithDomain:@"securityd" code:errSecDecode userInfo:@{NSLocalizedDescriptionKey: @"Failed to decode SignInAnalytics object"}]];
245 - (SFSignInAnalytics*)newSubTaskForEvent:(NSString*)eventName
247 SFSignInAnalytics *newSubTask = [[SFSignInAnalytics alloc] initChildWithSignInUUID:self.signin_uuid andCategory:self.category andEventName:self.eventName];
249 newSubTask.my_uuid = [NSUUID UUID].UUIDString;
250 newSubTask.parent_uuid = self.my_uuid;
251 newSubTask.signin_uuid = self.signin_uuid;
253 newSubTask.category = self.category;
254 newSubTask.eventName = [eventName copy];
255 newSubTask.root = self.root;
256 newSubTask.canceled = NO;
257 newSubTask.stopped = NO;
259 newSubTask.queue = dispatch_queue_create("com.apple.security.SignInAnalytics", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
260 newSubTask.tracker = [[SFSIALoggerObject logger] logSystemMetricsForActivityNamed:eventName withAction:nil];
261 [newSubTask.tracker start];
263 @synchronized(_eventDependencyList){
264 NSMutableArray *parentEntry = [newSubTask.root.eventDependencyList objectForKey:newSubTask.parent_uuid];
266 //add new subtask entry to parent event's list
267 [parentEntry addObject:newSubTask.my_uuid];
268 [newSubTask.root.eventDependencyList setObject:parentEntry forKey:newSubTask.parent_uuid];
270 //create new array list for this new subtask incase it has subtasks
271 [newSubTask.root.eventDependencyList setObject:[NSMutableArray array] forKey:newSubTask.my_uuid];
272 NSError* error = nil;
273 if(self.root.persistedEventPlist && ![newSubTask writeDependencyList:&error] ){
274 os_log(self.logObject, "attempting to write dependency list: %@", error);
282 - (void)logRecoverableError:(NSError*)error
286 os_log_error(self.logObject, "attempting to log a nil error for event:%@", self.eventName);
290 os_log_error(self.logObject, "%@", error);
292 NSMutableDictionary* eventAttributes = [NSMutableDictionary dictionary];
294 [eventAttributes setValuesForKeysWithDictionary:@{
295 SFSignInAnalyticsAttributeRecoverableError : @(YES),
296 SFSignInAnalyticsAttributeErrorDomain : error.domain,
297 SFSignInAnalyticsAttributeErrorCode : @(error.code),
298 SFSignInAnalyticsAttributeMyUUID : self.my_uuid,
299 SFSignInAnalyticsAttributeParentUUID : self.parent_uuid,
300 SFSignInAnalyticsAttributeSignInUUID : self.signin_uuid,
301 SFSignInAnalyticsAttributeEventName : self.eventName,
302 SFSignInAnalyticsAttributeSubsystemName : self.category
305 [[SFSIALoggerObject logger] logSoftFailureForEventNamed:self.eventName withAttributes:eventAttributes];
309 - (void)logUnrecoverableError:(NSError*)error
312 os_log_error(self.logObject, "attempting to log a nil error for event:%@", self.eventName);
316 os_log_error(self.logObject, "%@", error);
318 NSMutableDictionary* eventAttributes = [NSMutableDictionary dictionary];
320 [eventAttributes setValuesForKeysWithDictionary:@{
321 SFSignInAnalyticsAttributeRecoverableError : @(NO),
322 SFSignInAnalyticsAttributeErrorDomain : error.domain,
323 SFSignInAnalyticsAttributeErrorCode : @(error.code),
324 SFSignInAnalyticsAttributeMyUUID : self.my_uuid,
325 SFSignInAnalyticsAttributeParentUUID : self.parent_uuid,
326 SFSignInAnalyticsAttributeSignInUUID : self.signin_uuid,
327 SFSignInAnalyticsAttributeEventName : self.eventName,
328 SFSignInAnalyticsAttributeSubsystemName : self.category
331 [[SFSIALoggerObject logger] logHardFailureForEventNamed:self.eventName withAttributes:eventAttributes];
336 dispatch_sync(self.queue, ^{
337 [self.tracker cancel];
339 os_log(self.logObject, "canceled timer for %@", self.eventName);
343 - (void)stopWithAttributes:(NSDictionary<NSString*, id>*)attributes
345 dispatch_sync(self.queue, ^{
347 if(self.canceled || self.stopped){
355 NSMutableDictionary *mutableAttributes = nil;
358 mutableAttributes = [NSMutableDictionary dictionaryWithDictionary:attributes];
361 mutableAttributes = [NSMutableDictionary dictionary];
363 mutableAttributes[SFSignInAnalyticsAttributeMyUUID] = self.my_uuid;
364 mutableAttributes[SFSignInAnalyticsAttributeParentUUID] = self.parent_uuid;
365 mutableAttributes[SFSignInAnalyticsAttributeSignInUUID] = self.signin_uuid;
366 mutableAttributes[SFSignInAnalyticsAttributeEventName] = self.eventName;
367 mutableAttributes[SFSignInAnalyticsAttributeSubsystemName] = self.category;
368 mutableAttributes[SFSignInAnalyticsAttributeTrackerTime] = self.tracker.measurement;
370 [mutableAttributes enumerateKeysAndObjectsUsingBlock:^(NSString* key, id obj, BOOL * stop) {
371 os_log(self.logObject, "event: %@, %@ : %@", self.eventName, key, obj);
374 [[SFSIALoggerObject logger] logSuccessForEventNamed:self.eventName];
375 [[SFSIALoggerObject logger] logSoftFailureForEventNamed:self.eventName withAttributes:mutableAttributes];
379 -(BOOL) writeResultsToTmp {
381 bool shouldWriteResultsToTemp = NO;
382 CFBooleanRef toTmp = (CFBooleanRef)CFPreferencesCopyValue(CFSTR("DumpResultsToTemp"),
383 CFSTR("com.apple.security"),
384 kCFPreferencesAnyUser, kCFPreferencesAnyHost);
385 if(toTmp && CFGetTypeID(toTmp) == CFBooleanGetTypeID()){
386 if(toTmp == kCFBooleanFalse){
387 os_log(self.logObject, "writing results to splunk");
388 shouldWriteResultsToTemp = NO;
390 if(toTmp == kCFBooleanTrue){
391 os_log(self.logObject, "writing results to /tmp");
392 shouldWriteResultsToTemp = YES;
396 CFReleaseNull(toTmp);
397 return shouldWriteResultsToTemp;
400 - (void)processEventChainForUUID:(NSString*)uuid dependencyChain:(NSString*)dependencyChain
402 NSString* newChain = dependencyChain;
404 NSArray* children = [self.root.eventDependencyList objectForKey:uuid];
405 for (NSString* child in children) {
406 newChain = [NSString stringWithFormat:@"%@, %@", dependencyChain, child];
407 [self processEventChainForUUID:child dependencyChain:newChain];
409 if([children count] == 0){
410 [self.root.builtDependencyChains addObject:newChain];
411 os_log(self.logObject, "current dependency chain list: %@", newChain);
415 - (void)signInCompleted
418 os_log(self.logObject, "sign in complete");
419 NSError* error = nil;
421 //create dependency chains and log them
422 [self processEventChainForUUID:self.root.my_uuid dependencyChain:self.root.signin_uuid];
424 if([self.root.builtDependencyChains count] > 0){
425 NSDictionary* eventAttributes = @{SFSignInAnalyticsAttributeBuiltDependencyChains : self.root.builtDependencyChains};
426 [[SFSIALoggerObject logger] logSoftFailureForEventNamed:SFSignInAnalyticsAttributeBuiltDependencyChains withAttributes:eventAttributes];
429 if([self writeResultsToTmp]){ //writing sign in analytics to /tmp
430 os_log(self.logObject, "logging to /tmp");
432 NSData* eventData = [NSKeyedArchiver archivedDataWithRootObject:[[SFSIALoggerObject logger].database allEvents] requiringSecureCoding:YES error:&error];
434 [eventData writeToFile:SFSignInAnalyticsDumpLoggedResultsToLocation options:0 error:&error];
437 os_log_error(self.logObject, "error writing to file [%@], error:%@", SFSignInAnalyticsDumpLoggedResultsToLocation, error);
439 os_log(self.logObject, "successfully wrote sign in analytics to:%@", SFSignInAnalyticsDumpLoggedResultsToLocation);
442 os_log_error(self.logObject, "collected no data");
445 }else{ //writing to splunk
446 os_log(self.logObject, "logging to splunk");
449 if (self.persistencePath) {
450 //remove dependency list
451 BOOL removedPersistedDependencyList = [[NSFileManager defaultManager] removeItemAtPath:self.persistencePath error:&error];
452 if(!removedPersistedDependencyList || error){
453 os_log(self.logObject, "encountered error when attempting to remove persisted event list: %@", error);